package classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 
 * Custom class loader, with ability to compile class on fly, if needed
 * 
 * @author Bulaev_Igor
 *
 */

public class CompilingClassLoader extends ClassLoader {

	/**
	 * 
	 * Load class method implementation
	 * 
	 * @param loadedClass - Class we are trying to load
	 * @param name - name of the loadedClass
	 * @return Class object
	 * 
	 */
	
	@Override
	public Class loadClass(String name, boolean resolve)
			throws ClassNotFoundException {
		Class loadedClass = null;
		
		/** 1. Check if class already had been loaded */
		
		loadedClass = findLoadedClass(name);			
		if(loadedClass != null)
			return loadedClass;
		
		/** 2. Assemble required file names and search for them */
		
		String filename = name.replace('.', '\\');
		String javaFilename = "\\src\\" + filename + ".java";
		String classFilename = "\\bin\\" + filename + ".class";
		File javaFile = findFile(javaFilename);
		File classFile = findFile(classFilename);
		
		/** 3. If class or java file found, check if we need to compile */
		
		if (javaFile != null || classFile != null) {
			if (javaFile != null
					&& (classFile == null || javaFile.lastModified() > classFile
							.lastModified())) {
				try {
					if (!compile(javaFile.getAbsolutePath())
							|| findFile(classFilename) == null) {
						throw new ClassNotFoundException("Compile failed: "
								+ javaFilename);
					}
					classFile = findFile(classFilename);
				} catch (IOException ie) {
					throw new ClassNotFoundException(ie.toString());
				}
			}
			
			/** 4. After optional compile read file as array of bytes and put 
			 *      array in define method */
			
			try {
				byte raw[] = getBytes(classFile);
				loadedClass = defineClass(name, raw, 0, raw.length);
			} catch (IOException ie) {
				ie.printStackTrace();
			}
		}
		
		/** 5. If we still don't have class object, try to load it with system classloader */
		
		if (loadedClass == null) {
			loadedClass = findSystemClass(name);
		}
		
		/** 6. Resolve class if needed */
		
		if (resolve && loadedClass != null)
			resolveClass(loadedClass);
		
		/** 7. If class still wasn't found, throw an exception */
		
		if (loadedClass == null)
			throw new ClassNotFoundException(name);
		return loadedClass;
	}

	private File findFile(String name) {
		File test = new File(new File("").getAbsolutePath() + name);
		if (test.exists())
			return test;
		return null;
	}

	/**
	 * 
	 * Method that reads file into an array of bytes
	 * 
	 * @return array of bytes
	 * 
	 */
	
	private byte[] getBytes(File file) throws IOException {
		long len = file.length();
		byte raw[] = new byte[(int) len];
		FileInputStream fin = new FileInputStream(file);
		int r = fin.read(raw);
		if (r != len)
			throw new IOException("Can't read all, " + r + " != " + len);
		fin.close();
		return raw;
	}

	/**
	 * 
	 * Method that tries to compile selected file
	 * 
	 * @param javaFilePath - path to file that needs compilation
	 * @return true if compile successful, false otherwise
	 * 
	 */
	
	private boolean compile(String javaFilePath) throws IOException {
		System.out.println("CCL: Compiling " + javaFilePath + "...");
		Process p = Runtime.getRuntime().exec(
				"javac -d " + new File("").getAbsolutePath() + "\\bin\\ "
						+ javaFilePath);
		try {
			p.waitFor();
		} catch (InterruptedException ie) {
			System.out.println(ie);
		}
		int ret = p.exitValue();
		return ret == 0;
	}

}
